import frappe
from frappe import _
import json
from datetime import datetime, timedelta
import traceback
import uuid
#from mapping import batch_knowledge_service_chat
from matrix_fo.rest_api.mapping import batch_knowledge_service_chat


@frappe.whitelist()
def batch_copy_items(doc, child_rows, row_idx=1):
    """
    1. 使用销售订单明细客户镀种字段作为客户物料号，查找对应的模板物料
    2. 模板物料属性值查找多规格物料，属性值或多规格物料不存在则创建
    3. 模板物料属性名 = custom_销售订单明细字段名
    4. 因物料属性值不可为空，销售订单明细字段值为空时，使用NA代替
    """

    #from erpnext.stock.get_item_details import get_item_details
    from erpnext.controllers.item_variant import create_variant

    if isinstance(doc, str):
        doc = frappe.get_doc(json.loads(doc))
        child_rows = json.loads(child_rows)
    if isinstance(row_idx, str):
        row_idx = int(row_idx)    

    delivery_date = (datetime.today() + timedelta(days=2)).date()

    item = frappe.qb.DocType("Item")
    icd = frappe.qb.DocType("Item Customer Detail")
    template_item_map = frappe._dict(frappe.qb.from_(item
    ).join(icd
    ).on(item.name == icd.parent
    ).where(
        (item.has_variants==1) &
        (item.disabled==0) &
        (item.is_sales_item == 1) &
        (icd.ref_code.isin([r.get("custom_客户镀种") for r in child_rows]))
    ).select(
        icd.ref_code, 
        icd.parent
    ).run(as_list = 1))
    if not template_item_map:
        frappe.throw(_("与客户镀种对应的允许销售、未禁用的内部模板物料未创建!"))

    # 友荣客户特殊处理, 螺纹形/头形/杆径/杆长 作为规格字段
    if doc.customer == "YRJM":
        for row in child_rows:
            if not row.get("custom_规格"):
                    row["custom_规格"] = "".join([
                    row.get("custom_螺纹形") or "",
                    row.get("custom_头形") or "",
                    row.get("custom_杆径") or "",
                    row.get("custom_杆长") or ""
                ])
        
    template_item_code_attributes_map = {}
    template_item_codes = list(template_item_map.values())
    template_item_attributes = frappe.get_all("Item Variant Attribute",
        filters = {
            "parent": ("in", template_item_codes)
        }, fields = ["parent as item_code", "attribute"]
    )
    for row in template_item_attributes:
        template_item_code_attributes_map.setdefault(row.item_code, []).append(row.attribute)

    existing_item_codes = frappe.get_all("Item Variant Attribute",
        filters = {
            "variant_of": ("in", template_item_codes),
        }, fields = ["parent as item_code", "variant_of as template_item_code", "attribute", "attribute_value"]
    )
    item_code_data_map = {}
    if existing_item_codes:
        item_code_data_list = frappe.get_all("Item",
            filters = {
                "name": ("in", {r.item_code for r in existing_item_codes})
            }, fields=["name as item_code", "item_name", "stock_uom as uom"]
        )

        item_code_data_map = {r.item_code:r for r in item_code_data_list}
    item_attribute_map, attr_values_item_code_map = {}, {}
    for row in existing_item_codes:
        item_attribute_map.setdefault((row.item_code, row.template_item_code), {})[row.attribute] = row.attribute_value
        
    for ((item_code, template_item_code), attr_values) in item_attribute_map.items():
        attributes = template_item_code_attributes_map.get(template_item_code, [])
        # globals().update(locals()) #用于在bench console调试
        str_attr_values = ",".join("{0}:{1}".format(attr, attr_values.get(attr)) for attr in attributes)					
        attr_values_item_code_map[str_attr_values] = item_code

    attribute_doc_map = {}

    def get_attribute_doc(attr, attribute_doc_map):
        attr_doc = attribute_doc_map.get(attr)
        if not attr_doc:
            attr_doc = frappe.get_doc("Item Attribute", attr)
        attribute_doc_map[attr] = attr_doc
        return attr_doc

    for row in child_rows:
        customer_service_item = row.get("custom_客户镀种")
        template_item_code = template_item_map.get(customer_service_item)
        if not template_item_code:
            frappe.throw(_("template item code not found for customer service item {0}").format(customer_service_item))
        else:
            # 基于模板物料的属性值组合查找多规格物料，不存在则创建
            attributes = template_item_code_attributes_map.get(template_item_code, [])            
            str_attr_values = ",".join("{0}:{1}".format(attr, row.get(f'custom_{attr}') or "NA") for attr in attributes)
            item_code = attr_values_item_code_map.get(str_attr_values)
            if item_code:
                row["item_code"] = item_code
                item_dict = item_code_data_map.get(row.get("item_code"))
                row.update(item_dict)
            else:
                attribute_dict = {}
                # 查找属性值，不存在则创建
                for attr in attributes:
                    attr_value = row.get(f'custom_{attr}') or "NA"
                    attr_doc = get_attribute_doc(attr, attribute_doc_map)
                    # globals().update(locals()) #用于在bench console调试
                    if not [a for a in attr_doc.item_attribute_values if a.attribute_value==attr_value]:
                        attr_doc.append("item_attribute_values", {"attribute_value": attr_value, "abbr": attr_value})
                        attr_doc.save(ignore_permissions=1)
                    attribute_dict.update({attr: attr_value})
                variant = create_variant(item=template_item_code, args=attribute_dict)
                custom_fields_to_sync = ["custom_数量单位","custom_膜厚要求","custom_终客户","custom_材质", "weight_per_unit",
                                         "custom_封闭要求", "custom_除氢要求", "custom_盐雾要求", "custom_外径控制","custom_热处理"]
                for field in custom_fields_to_sync:
                    if hasattr(variant, field):
                        variant.set(field, row.get(field) or None)
                if hasattr(variant, "custom_客户"):
                    variant.set("custom_客户", doc.customer)
                variant.save(ignore_permissions=1)
                new_item_code = variant.name
                # 用于后续行匹配相同的多规格物料
                attr_values_item_code_map[str_attr_values] = new_item_code
                item_code_data_map[new_item_code] = {
                    "item_name": variant.item_name,
                    "uom": variant.stock_uom
                }
                row.update({
                    "item_code": new_item_code,
                    "item_name": variant.item_name,
                    "uom": variant.stock_uom
                })
                                
    # if disabled_items_errors:
    #     frappe.msgprint(','.join(disabled_items_errors))
    # if non_sales_items_errors:
    #     frappe.msgprint(','.join(non_sales_items_errors))

    # #支持多次追加复制    
    doc.items = doc.items[0:len(doc.items)-len(child_rows)]
    for row in child_rows:
        if row.get('item_code'):
            row["delivery_date"] = delivery_date
            # 以重量作为数量（数量以重量为单位）
            if not row.get("qty") and row.get("custom_重量"):
                try:
                    row["qty"] = float(str(row.get("custom_重量")).replace(",", ""))
                except ValueError:
                    frappe.throw(f"无效的数量格式：{row.get('custom_重量')}")

            doc.append('items', row)
    doc.set("delivery_date", delivery_date)
    return doc


# def get_template_item_map_batch_fallback(child_rows, customer_code=None):
#     item = frappe.qb.DocType("Item")
#     icd = frappe.qb.DocType("Item Customer Detail")

#     镀种_list = list({row.get("custom_客户镀种") for row in child_rows if row.get("custom_客户镀种")})
#     result_map = {}

#     # Step 1: 带客户编号的查找
#     if customer_code:
#         results = frappe.qb.from_(item).join(icd).on(item.name == icd.parent).where(
#             (item.has_variants == 1) &
#             (item.disabled == 0) &
#             (item.is_sales_item == 1) &
#             (icd.ref_code.isin(镀种_list)) &
#             (icd.customer_name == customer_code)
#         ).select(icd.ref_code, icd.parent).run(as_list=True)

#         for ref_code, parent in results:
#             result_map[ref_code] = parent

#     # Step 2: 剩余的镀种，不带客户编号查找
#     unmatched = [code for code in 镀种_list if code not in result_map]

#     if unmatched:
#         fallback_results = frappe.qb.from_(item).join(icd).on(item.name == icd.parent).where(
#             (item.has_variants == 1) &
#             (item.disabled == 0) &
#             (item.is_sales_item == 1) &
#             (icd.ref_code.isin(unmatched))
#         ).select(icd.ref_code, icd.parent).run(as_list=True)

#         for ref_code, parent in fallback_results:
#             result_map[ref_code] = parent

#         still_missing = [code for code in unmatched if code not in result_map]
#         if still_missing:
#             frappe.throw(_("以下客户镀种未找到模板物料: {0}").format(", ".join(still_missing)))

#     return frappe._dict(result_map)

def get_template_item_map_batch_fallback(child_rows, customer_code=None):
    """
    生成 { "镀种-终客户": 模板物料 , "镀种": 模板物料 } 的映射。
    优先级：
      A) 客户 + 终客户 + 镀种
      B) 客户 + 镀种
      C) 仅镀种
    """
    item = frappe.qb.DocType("Item")
    icd  = frappe.qb.DocType("Item Customer Detail")

    def _s(v):  # 规范化字符串
        return (v or "").strip()

    def _k(plating, endcust=None):  # 组装 key
        plating = _s(plating)
        endcust = _s(endcust)
        return f"{plating}-{endcust}" if endcust else plating

    # 从行里收集所有“镀种”和“终客户”
    plating_list = sorted({ _s(r.get("custom_客户镀种")) for r in child_rows if _s(r.get("custom_客户镀种")) })
    # 注意：A层不需要先收集终客户列表；直接把查到的每条记录里的 custom_out_custom_no 当作 key 的后半段即可

    # --- A: 客户 + 终客户 + 镀种（用 ICD.custom_out_custom_no，而不是 Item 上的字段）---
    level_a = {}
    if customer_code and plating_list:
        rows = (
            frappe.qb.from_(item)
            .join(icd).on(item.name == icd.parent)
            .where(
                (item.has_variants == 1)
                & (item.disabled == 0)
                & (item.is_sales_item == 1)
                & (icd.ref_code.isin(plating_list))
                & (icd.customer_name == customer_code)
                & (icd.custom_out_custom_no.isnotnull())
            )
            .select(icd.ref_code, icd.custom_out_custom_no, icd.parent)
            .run(as_list=True)
        )
        for ref_code, out_cust, parent in rows:
            key = _k(ref_code, out_cust)
            if key and key not in level_a:
                level_a[key] = parent

    # --- B: 客户 + 镀种 ---
    level_b = {}
    if customer_code and plating_list:
        rows = (
            frappe.qb.from_(item)
            .join(icd).on(item.name == icd.parent)
            .where(
                (item.has_variants == 1)
                & (item.disabled == 0)
                & (item.is_sales_item == 1)
                & (icd.ref_code.isin(plating_list))
                & (icd.customer_name == customer_code)
            )
            .select(icd.ref_code, icd.parent)
            .run(as_list=True)
        )
        for ref_code, parent in rows:
            key = _k(ref_code)  # 只有镀种
            if key and key not in level_b:
                level_b[key] = parent

    # --- C: 仅镀种（通用）---
    level_c = {}
    if plating_list:
        rows = (
            frappe.qb.from_(item)
            .join(icd).on(item.name == icd.parent)
            .where(
                (item.has_variants == 1)
                & (item.disabled == 0)
                & (item.is_sales_item == 1)
                & (icd.ref_code.isin(plating_list))
            )
            .select(icd.ref_code, icd.parent)
            .run(as_list=True)
        )
        for ref_code, parent in rows:
            key = _k(ref_code)
            if key and key not in level_c:
                level_c[key] = parent

    # 合并三层，A > B > C
    result_map = {}
    for src in (level_c, level_b, level_a):  # 先低后高，再用 setdefault 保留高优先级
        for k, v in src.items():
            result_map.setdefault(k, v)

    return frappe._dict(result_map)




@frappe.whitelist()
def batch_copy_items_2(doc, child_rows, row_idx=1):
    if isinstance(doc, str):
        doc = frappe.get_doc(json.loads(doc))
        child_rows = json.loads(child_rows)
    if isinstance(row_idx, str):
        row_idx = int(row_idx)
    
    delivery_date = (datetime.today() + timedelta(days=2)).date()

    item = frappe.qb.DocType("Item")
    icd = frappe.qb.DocType("Item Customer Detail")

    template_item_map = get_template_item_map_batch_fallback(child_rows, customer_code=doc.customer)
    # frappe.throw("报错测试: " + str(template_item_map))
    # 根据客户镀种字段查找对应的模板物料（必须是允许销售、未禁用的多规格模板）
    """
    template_item_map = frappe._dict(frappe.qb.from_(item
    ).join(icd
    ).on(item.name == icd.parent
    ).where(
        (item.has_variants==1) &
        (item.disabled==0) &
        (item.is_sales_item == 1) &
        (icd.ref_code.isin([r.get("custom_客户镀种") for r in child_rows]))
    ).select(
        icd.ref_code,
        icd.parent
    ).run(as_list = 1))

    if not template_item_map:
            frappe.throw(_("与客户镀种对应的允许销售、未禁用的内部模板物料未创建!"))
    """

    # 如果客户是 YRJM，拼接规格字段
    if doc.customer == "YRJM":
        for row in child_rows:
            if not row.get("custom_规格"):
                # 清洗数值格式：将"0.00"转换为"0"，去除末尾的".00"
                def clean_value(val):
                    if val is None:
                        return ""
                    val = str(val)
                    return val.rstrip('0').rstrip('.') if '.' in val else val

                thread_type = row.get("custom_螺纹形") or ""
                head_type = row.get("custom_头形") or ""
                diameter = clean_value(row.get("custom_杆径"))
                length = clean_value(row.get("custom_杆长"))

                row["custom_规格"] = f"{thread_type} {head_type} {diameter}*{length}"

    attributes = ["custom_规格", "custom_客户料号", "custom_终客户"]

    template_item_codes = list(template_item_map.values())
    items = frappe.get_all("Item",
        filters = {
            "custom_内部镀种": ("in", template_item_codes),
            "disabled": 0,
            "custom_客户": doc.customer
        }, fields = ["item_code", "custom_内部镀种 as template_item_code"] + attributes
    )
    item_code_data_list = frappe.get_all("Item",
            filters = {
                "name": ("in", {r.item_code for r in items})
            }, fields=["name as item_code", "item_name", "stock_uom as uom"]
        )
    item_code_data_map = {}
    item_code_data_map = {r.item_code: r for r in item_code_data_list}
    attr_values_item_code_map = {}
    
    for item in items:
        str_attr_values = "-".join(str(item.get(attr) or "NA") for attr in attributes)
        attr_values_item_code_map[str_attr_values] = item.item_code

    # frappe.throw("报错测试: " + str(attr_values_item_code_map))
    for row in child_rows:
        customer_service_item = row.get("custom_客户镀种")
        endcust = (row.get("custom_终客户") or "").strip()
        key_with_endcust = f"{customer_service_item}-{endcust}" if endcust else customer_service_item
        # 有终客户优先根据终客户取内部镀种
        template_item_code = template_item_map.get(key_with_endcust)

        if not template_item_code:
            template_item_code = template_item_map.get(customer_service_item)

        if not template_item_code:
            frappe.throw(_("template item code not found for customer service item {0}").format(customer_service_item))
        else:
            # 保证关键属性字段非空
            for attr in attributes:
                if not row.get(attr):
                    row[attr] = "NA"
                str_attr_values = "-".join(str(row.get(attr) or "NA") for attr in attributes)
            item_code = attr_values_item_code_map.get(str_attr_values)

            if item_code:
                row["item_code"] = item_code
                item_dict = item_code_data_map.get(row.get("item_code"))
                row.update(item_dict)
            else:
                new_item = frappe.new_doc("Item")

                # 设置物料基础信息
                new_item.update({
                    # "item_code": row.get("item_code"),  # 需要确保有值且唯一
                    "item_code": "WTEST-" + str(uuid.uuid4())[:8],
                    "item_name": str_attr_values,
                    "item_group": "服务", 
                    "stock_uom": row.get("uom") or "kg",
                    "is_stock_item": 0, #默认不开启库存管理
                    "is_purchase_item": 0, #默认不允许采购
                    "custom_内部镀种": template_item_code
                })

                # 同步部分字段
                custom_fields_to_sync = [
                    "custom_数量单位", "custom_膜厚要求", "custom_终客户", "custom_材质", "weight_per_unit",
                    "custom_封闭要求", "custom_除氢要求", "custom_盐雾要求", "custom_外径控制", "custom_热处理",
                    "custom_客户镀种", "custom_螺纹形", "custom_头形", "custom_杆径", "custom_杆长"
                ]
                custom_fields_to_sync = custom_fields_to_sync + attributes
                for field in custom_fields_to_sync:
                    if hasattr(new_item, field):
                        value = row.get(field)
                        new_item.set(field, value or None)
                if hasattr(new_item, "custom_客户"):
                    new_item.set("custom_客户", doc.customer)

                try:
                    new_item.insert(ignore_permissions=1)
                except frappe.DuplicateEntryError:
                    frappe.throw(f"物料 {new_item.item_code} 已存在！")
                except Exception as e:
                    frappe.throw(f"创建失败：{frappe.get_traceback()}")
                    # frappe.throw(f"创建失败：{str(e)}")
                new_item_code = new_item.name
                attr_values_item_code_map[str_attr_values] = new_item_code
                item_code_data_map[new_item_code] = {
                    "item_name": new_item.item_name,
                    "uom": new_item.stock_uom
                }
                row.update({
                    "item_code": new_item_code,
                    "item_name": new_item.item_name,
                    "uom": new_item.stock_uom,
                    "custom_首批": 1
                })

    doc.items = doc.items[0:len(doc.items)-len(child_rows)]

    for row in child_rows:
        if row.get('item_code'):
            # 设置销售出货日期
            row["delivery_date"] = delivery_date
            # 重新赋值重量
            if not row.get("qty") and row.get("custom_重量"):
                try:
                    row["qty"] = float(str(row.get("custom_重量")).replace(",", ""))
                except ValueError:
                    frappe.throw(f"无效的数量格式：{row.get('custom_重量')}")
            # 重新赋值返工字段
            if row.get("custom_是否返工") is not None:
                val = row.get("custom_是否返工")
                # 传入 "返工"，转换为 "是"
                if val == "返工":
                    row["custom_是否返工"] = "是"
                # 如果已经是 "是" 或 "否"，保持原样
                elif val in ("是", "否", "代返工"):
                    row["custom_是否返工"] = val
                # 其他值默认 "否"
                else:
                    row["custom_是否返工"] = "否"
            # 重新赋值返工字段
            if row.get("custom_是否急品") is not None:
                val = row.get("custom_是否急品")
                if val in ("是", "否", "特急品"):
                    row["custom_是否急品"] = "是"
                else:
                    row["custom_是否急品"] = "否"
            doc.append('items', row)

    # ===== 最后一步：从物料主数据回填缺失的要求字段 =====
    REQUIRED_ATTRS = [
        "custom_封闭要求", "custom_除氢要求", "custom_盐雾要求", "custom_外径控制", "custom_热处理", "custom_膜厚要求", "custom_材质",
        "custom_螺纹形", "custom_头形", "custom_杆径", "custom_杆长"
    ]

    # 仅保留 Item 上确实存在的字段，避免 schema 差异报错
    item_meta = frappe.get_meta("Item")
    item_fields = [f for f in REQUIRED_ATTRS if item_meta.has_field(f)]
    if item_fields:
        # 收集本单据所有 item_code（去重）
        item_codes = []
        for d in (doc.get("items") or []):
            code = (d.get("item_code") if isinstance(d, dict) else getattr(d, "item_code", None))
            if code:
                item_codes.append(code)
        item_codes = list(set(item_codes))

        if item_codes:
            # 一次性批量取回物料主数据上的这些字段
            rows = frappe.get_all(
                "Item",
                filters={"name": ("in", item_codes)},
                fields=["name"] + item_fields,
            )
            item_attr_map = {r["name"]: r for r in rows}

            # 定义“是否视为缺值”的判断：None / 空串视为缺值，0/False 不算缺
            def _is_missing(val):
                if val is None:
                    return True
                if isinstance(val, str) and val.strip() == "":
                    return True
                return False

            # 回填：只有当前行为空才从 Item 取值
            for d in (doc.get("items") or []):
                code = (d.get("item_code") if isinstance(d, dict) else getattr(d, "item_code", None))
                if not code:
                    continue
                src = item_attr_map.get(code) or {}
                for f in item_fields:
                    cur = (d.get(f) if isinstance(d, dict) else getattr(d, f, None))
                    if _is_missing(cur):
                        val = src.get(f)
                        if not _is_missing(val):
                            if hasattr(d, "set"):
                                d.set(f, val)  # 子表行是 Document 时
                            else:
                                d[f] = val     # 子表行是 dict 时
    # 设置交货日期字段
    doc.set("delivery_date", delivery_date)
    return doc  

@frappe.whitelist(allow_guest=False)
def recive_sales_order(data):
    # 如果是字符串，先转为 dict
    if isinstance(data, str):
        try:
            data = json.loads(data)
        except json.JSONDecodeError:
            return {
                "status": "error",
                "message": "无法解析 JSON 数据，请检查格式"
            }

    # 校验顶层数据结构
    if not isinstance(data, dict) or "items" not in data:
        return {
            "status": "error",
            "message": "JSON结构应为包含'客户'、'预计交期'和'items'字段的字典"
        }

    # 提取销售订单基本信息
    so_data = {
        "doctype": "Sales Order",
        "customer": data.get("客户"),
        "delivery_date": (datetime.today() + timedelta(days=2)).date(),
        "items": []
    }

    # 检查 items 是否为列表
    items = data["items"]
    if not isinstance(items, list):
        return {
            "status": "error",
            "message": "'items' 字段应为列表"
        }

    try:
        result = [item["客户镀种"] for item in items if item.get("create_if_not_exists") is True]
    except KeyError:
        return {
            "status": "error",
            "message": "部分 item 缺少 '客户镀种' 字段"
        }

    batch_results = batch_knowledge_service_chat(result, delay=0.5)

    coating_map = {
        key: value
        for d in batch_results.get("data", [])
        for key, value in d.items()
        }
    
    item_maps = {}
    item_code_list = []
    # 遍历每一项，填充到 Sales Order items
    for entry in items:

        create_map = {
                "create_if_not_exists":entry.get("create_if_not_exists")
        }
        
        if create_map["create_if_not_exists"] :

            item_key = (
                (entry.get("规格") or "NA") + "-" +
                (entry.get("客户料号") or "NA") + "-" +
                (entry.get("客户镀种") or "NA") + "-" +
                (entry.get("终客户") or "NA")
                )

            if not item_maps.get(item_key):

                # coating = entry.get("客户镀种")
                # item_no = coating_map.get(coating)
                new_item = {
                    "doctype": "Item",
                    "item_code": "AUTO_ITEM_CODE" + str(uuid.uuid4())[:8],
                    "stock_uom": entry.get("重量单位") or "kg",
                    "item_group": "服务",
                    "is_stock_item": 0, #默认不开启库存管理
                    "is_purchase_item": 0, #默认不允许采购
                    "custom_内部镀种":  entry.get("内部镀种号"),
                    "item_name": (
                        (entry.get("规格") or "NA") + "-" +
                        (entry.get("客户料号") or "NA") + "-" +
                        (entry.get("终客户") or "NA")),
                    "custom_客户":data.get("客户"),
                    "custom_规格": entry.get("规格"),
                    "custom_客户料号": entry.get("客户料号"),
                    "custom_客户镀种": entry.get("客户镀种"),
                    "custom_终客户":entry.get("终客户"),
                    "custom_膜厚要求": entry.get("膜厚要求"),
                    "custom_封闭要求": entry.get("封闭要求"),
                    "custom_盐雾要求": entry.get("盐雾要求"),
                    "custom_除氢要求": entry.get("除氢要求"),
                    "custom_热处理": entry.get("热处理"),
                    "custom_螺纹形": entry.get("螺纹形"),
                    "custom_头形": entry.get("头形"),
                    "custom_杆径": entry.get("杆径"),
                    "custom_杆长": entry.get("杆长"),
                    "custom_外径控制": entry.get("外径控制")
                }
                # passed_item_code = entry.get("物料号")
                # if passed_item_code:
                #     new_item.update({
                #         "item_group": "产品",
                #         "item_code": passed_item_code,
                #         })
                # else:
                #     new_item.update({
                #         "item_group": "服务",
                #         "item_code": "AUTO_ITEM_CODE",
                #         })
                try:
                    item_new =  frappe.get_doc(new_item)
                    item_new.insert(ignore_permissions=True)

                    item_maps[item_key] = item_new.item_code

                    # 如果是传了物料号的，创建后改为服务组
                    # if passed_item_code:
                    #     item_new.item_group = "服务"
                    #     item_new.save()
                except Exception as e:
                    frappe.throw(f"创建失败：{frappe.get_traceback()}")
                    # frappe.throw(f"创建失败：{str(e)}")

                item_data = {
                    item_maps[item_key]:{
                        "规格":entry.get("规格")or "NA",
                        "客户料号":entry.get("客户料号") or "NA",
                        "客户镀种":entry.get("客户镀种") or "NA",
                        "终客户":entry.get("终客户") or "NA",
                        "内部镀种号":entry.get("内部镀种号"),
                        "内部镀种":entry.get("内部镀种"),
                        "客户":data.get("客户")
                    }
                }

        # 重新赋值返工字段
        if entry.get("是否返工") is not None:
            val = entry.get("是否返工")
            # 传入 "返工"，转换为 "是"
            if val == "返工":
                entry["是否返工"] = "是"
            # 如果已经是 "是","否","代返工"，保持原样
            elif val in ("是", "否", "代返工"):
                entry["是否返工"] = val
            # 其他值默认 "否"
            else:
                entry["是否返工"] = "否"
        
        item = {
            #"item_code": entry.get("物料号") or item_new.item_code,
            "item_name": entry.get("客户镀种"),
            "qty": float(entry.get("重量", 0) or 0),
            "uom": entry.get("重量单位"),

            # 自定义字段（请确保这些字段已在 Sales Order Item 中创建）
            "custom_规格": entry.get("规格"),
            "custom_客户料号": entry.get("客户料号"),
            "custom_客户镀种": entry.get("客户镀种"),
            "custom_箱数": entry.get("箱数"),
            "custom_批次": entry.get("批次"),
            "custom_数量": entry.get("数量"),
            "custom_数量单位": entry.get("数量单位"),
            "custom_交期要求": entry.get("交期要求"),
            "custom_膜厚要求": entry.get("膜厚要求"),
            "custom_封闭要求": entry.get("封闭要求"),
            "custom_盐雾要求": entry.get("盐雾要求"),
            "custom_除氢要求": entry.get("除氢要求"),
            "custom_热处理": entry.get("热处理"),
            "custom_螺纹形": entry.get("螺纹形"),
            "custom_头形": entry.get("头形"),
            "custom_杆径": entry.get("杆径"),
            "custom_杆长": entry.get("杆长"),
            "custom_外径控制": entry.get("外径控制"),
            "custom_是否样品": entry.get("是否样品"),
            "custom_是否返工": entry.get("是否返工"),
            "custom_客户订单号": entry.get("客户订单号"),
            "custom_材质": entry.get("材质"),
            "custom_备注": entry.get("备注")
        }
        if create_map["create_if_not_exists"]:
            item["item_code"] = item_maps.get(item_key)
            item_code_list.append(item_data)
        else:
            item["item_code"] = entry.get("物料号")

        so_data["items"].append(item)
        
    # sales_order.submit()提交销售订单

    # 创建销售订单
    try:
        so = frappe.get_doc(so_data)
        so.insert(ignore_permissions=True)  # 视权限情况可设置 True
        return {
            "status": "success",
            "message": f"销售订单已创建：{so.name},物料已创建：{item_code_list}"
        }
    except Exception as e:
        frappe.log_error(frappe.get_traceback(), "创建销售订单失败")
        return {
            "status": "error",
            "message": f"创建销售订单失败：{str(e)}"
        }

@frappe.whitelist()
def get_custom_qtys_batch(so_detail_names):
    '''
    获取可出库数量, 过滤销售订单行关闭的数据
    '''
    import json

    # 将 JSON 字符串转为 Python 列表
    if isinstance(so_detail_names, str):
        so_detail_names = json.loads(so_detail_names)

    result = {}

    # 批量查询
    sales_order_items = frappe.get_all(
        "Sales Order Item",
        filters={"name": ["in", so_detail_names]},
        fields=["name", "custom_可出库数量", "custom_line_status"]
    )

    for item in sales_order_items:
        # 必须属于正常或者部分交货
        if item.custom_line_status in {"NORMAL", "PARTIAL_DELIVERY"}:
            result[item.name] = item.custom_可出库数量 or 0

    return result
